package web

import (
	"context"
	"net/http"
	"net/http/pprof"

	"gddgame.cc/galaxy/server/web/middleware"

	"github.com/go-chi/chi"
)

type Handler = middleware.Handler

type Next = middleware.Next

var (
	// RouteCtxKey is the context.Context key to store the request context.
	RouteCtxKey = &contextKey{"WebContext"}
	defaultNext = func(err error) error { return err }
)

func single(router *router, middle Handler, next Next) http.HandlerFunc {
	return func(writer http.ResponseWriter, request *http.Request) {
		ctx := RouteContext(request.Context())
		_ = middle(ctx, next)
	}
}

func batch(middles []Handler, next Next) http.HandlerFunc {
	return func(writer http.ResponseWriter, request *http.Request) {
		ctx := RouteContext(request.Context())
		//fmt.Println(middles, len(middles))
		if err := m(middles, ctx, len(middles), 0, next); err != nil {
			ctx.SetResponse(err)
		}
	}
}

func batchHandler(middles []Handler) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			ctx := RouteContext(r.Context())
			if err := m(middles, ctx, len(middles), 0, func(err error) error {
				next.ServeHTTP(w, r)
				return nil
			}); err != nil {
				ctx.SetResponse(err)
			}
		})
	}
}

func m(middles []Handler, context Context, all int, index int, next Next) error {
	return middles[index](context, func(err error) error {
		if err != nil {
			return next(err)
		} else if index+1 >= all {
			return next(nil)
		} else {
			return m(middles, context, all, index+1, next)
		}
	})
}

type ControllerHandler func(context Context) interface{}

type Router interface {
	Use(middles ...Handler)
	Group(pattern string) Router
	Handle(pattern string, handler http.Handler)

	Connect(pattern string, middles ...Handler)
	Delete(pattern string, middles ...Handler)
	Get(pattern string, middles ...Handler)
	Head(pattern string, middles ...Handler)
	Options(pattern string, middles ...Handler)
	Patch(pattern string, middles ...Handler)
	Post(pattern string, middles ...Handler)
	Put(pattern string, middles ...Handler)
	Trace(pattern string, middles ...Handler)

	FileServer(localPath string, targetPath string)
	NotFound(middle Handler)
	MethodNotAllowed(middle Handler)
}

func RouteContext(ctx context.Context) Context {
	c := ctx.Value(RouteCtxKey)
	if c != nil {
		return c.(Context)
	}
	return nil
}

type contextKey struct {
	name string
}

func (k *contextKey) String() string {
	return "chi context value " + k.name
}

type router struct {
	router chi.Router
	root   chi.Router
	engine *engine
}

func newRouter(engine *engine) *router {
	router := &router{}
	router.root = chi.NewRouter()
	router.engine = engine

	return router
}

func (r *router) CommonMiddleware() {
	r.Use(middleware.RealIp)
}

func (r *router) debug() {
	r.root.Use(batchHandler([]Handler{middleware.Print}))
	r.root.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
	r.root.HandleFunc("/debug/pprof/profile", pprof.Profile)
	r.root.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
	r.root.HandleFunc("/debug/pprof/trace", pprof.Trace)
	r.root.HandleFunc("/debug/pprof/goroutine", pprof.Index)
	r.root.HandleFunc("/debug/pprof/allocs", pprof.Index)
	r.root.HandleFunc("/debug/pprof/block", pprof.Index)
	r.root.HandleFunc("/debug/pprof/heap", pprof.Index)
	r.root.HandleFunc("/debug/pprof/mutex", pprof.Index)
	r.root.HandleFunc("/debug/pprof/threadcreate", pprof.Index)
	r.root.HandleFunc("/debug/pprof/", pprof.Index)
}

func (r *router) init() {
	r.router = r.root.With()
}

func (r *router) Use(middlers ...Handler) {
	r.router.Use(batchHandler(middlers))
}
func (r *router) Group(pattern string) Router {
	return &router{router: r.router.Route(pattern, nil), root: r.root, engine: r.engine}
}
func (r *router) Handle(pattern string, handler http.Handler) {
	r.root.Handle(pattern, handler)
}
func (r *router) Get(pattern string, middles ...Handler) {
	r.router.Get(pattern, batch(middles, defaultNext))
}
func (r *router) Post(pattern string, middles ...Handler) {
	r.router.Post(pattern, batch(middles, defaultNext))
}
func (r *router) Put(pattern string, middles ...Handler) {
	r.router.Put(pattern, batch(middles, defaultNext))
}
func (r *router) Delete(pattern string, middles ...Handler) {
	r.router.Delete(pattern, batch(middles, defaultNext))
}
func (r *router) Connect(pattern string, middles ...Handler) {
	r.router.Connect(pattern, batch(middles, defaultNext))
}
func (r *router) Options(pattern string, middles ...Handler) {
	r.router.Options(pattern, batch(middles, defaultNext))
}
func (r *router) Head(pattern string, middles ...Handler) {
	r.router.Head(pattern, batch(middles, defaultNext))
}
func (r *router) Trace(pattern string, middles ...Handler) {
	r.router.Trace(pattern, batch(middles, defaultNext))
}
func (r *router) Patch(pattern string, middles ...Handler) {
	r.router.Patch(pattern, batch(middles, defaultNext))
}
func (r *router) NotFound(middle Handler) {
	r.root.NotFound(single(r, middle, defaultNext))
}
func (r *router) MethodNotAllowed(middle Handler) {
	r.router.MethodNotAllowed(single(r, middle, defaultNext))
}
func (r *router) FileServer(localPath string, targetPath string) {
	root := http.Dir(localPath)
	fs := http.StripPrefix(targetPath, http.FileServer(root))

	if targetPath != "/" && targetPath[len(targetPath)-1] != '/' {
		r.router.Get(targetPath, http.RedirectHandler(targetPath+"/", 301).ServeHTTP)
		targetPath += "/"
	}
	targetPath += "*"

	r.router.Get(targetPath, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fs.ServeHTTP(w, r)
	}))
}

func ControllerHandle(controller ControllerHandler) Handler {
	return func(context Context, next Next) error {
		// err也直接返回，在response中判断类型
		response := controller(context)
		context.SetResponse(response)
		return next(nil)
	}
}
